using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

using Pegasus.Log4Net;
using Pegasus.Log4Net.Appender;
using Pegasus.Log4Net.Core;
using Pegasus.Log4Net.Repository;
using Pegasus.Log4Net.Repository.Hierarchy;
using Pegasus.Reflection;
using Pegasus.Windows.Forms;

namespace Pegasus.Windows.Forms.Log4Net
{
	/// <summary>
	/// This dialog form is used to configure the Log4Net at runtime.
	/// </summary>
	internal partial class FormConfigLog4Net : FormEx
	{
		// Local Instance Values
		private LevelMenuItem m_menuItemParent;
		private IAppender m_appender;

		// Local Static Values
		private static readonly Level[] s_standardLevels = new Level[]
		{
			Level.All,
			Level.Debug,
			Level.Info,
			Level.Warn,
			Level.Error,
			Level.Fatal,
			Level.Off,
		};

		private const string LEVEL_PERDEFINED = "PredefinedLevels";

		/// <summary>
		/// Initializes a new instance of the <see cref="FormConfigLog4Net"/> class.
		/// </summary>
		/// <param name="serviceName">Name of the application (used to save settings).</param>
		/// <param name="appender">The appender to use when adding new loggers to the system.</param>
		public FormConfigLog4Net( string serviceName, IAppender appender )
			: base( serviceName + "DebugModeSettings2" )
		{
			m_appender = appender;

			InitializeComponent();

			if( UserData.ContainsKey( LEVEL_PERDEFINED ) )
			{
				CheckPredefinedLevels.Checked = (bool) UserData[ LEVEL_PERDEFINED ];
			}

			BuildDropList();
			BuildContextMenu();
			BuildTree();
		}

		/// <summary>
		/// Handles the Click event of the ButtonAdd control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ButtonAdd_Click( object sender, EventArgs args )
		{
			string text = ComboNamespace.Text.Trim();
			if( !string.IsNullOrEmpty( text ) )
			{
				Hierarchy hierarchy = (Hierarchy) LogManager.GetRepository();
				Logger newLogger = hierarchy.GetLogger( text ) as Logger;
				newLogger.AddAppender( m_appender );
			}

			BuildTree();
		}

		/// <summary>
		/// Handles the MouseClick event of the TreeLoggers control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
		private void TreeLoggers_MouseClick( object sender, MouseEventArgs args )
		{
			if( args.Button == MouseButtons.Right )
			{
				TreeNode node = TreeLoggers.GetNodeAt( args.Location );
				if( node != null )
				{
					TreeLoggers.SelectedNode = node;
				}
			}
		}

		/// <summary>
		/// Handles the CheckedChanged event of the CheckPredefinedLevels control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void CheckPredefinedLevels_CheckedChanged( object sender, EventArgs args )
		{
			UserData[ LEVEL_PERDEFINED ] = CheckPredefinedLevels.Checked;
			BuildContextMenu();
		}

		/// <summary>
		/// Handles the Opening event of the ContextMenuLevel control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
		private void ContextMenuLevel_Opening( object sender, CancelEventArgs args )
		{
			TreeNode selectedNode = TreeLoggers.SelectedNode;
			if( selectedNode != null )
			{
				Logger logger = (Logger) selectedNode.Tag;
				Level level = logger.Level;

				m_menuItemParent.Enabled = ( logger.Parent != null );

				// Set the proper checked state for all the menu items
				foreach( object obj in ContextMenuLevel.Items )
				{
					LevelMenuItem item = obj as LevelMenuItem;
					if( item != null )
					{
						item.Checked = ( (Level) item.Tag == level );
					}
				}
			}
			else
			{
				args.Cancel = true;
			}
		}

		/// <summary>
		/// Handles the Click event of the ContextMenuLevel control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ContextMenuLevel_Click( object sender, EventArgs args )
		{
			Level newLevel = (Level) ( (LevelMenuItem) sender ).Tag;
			TreeNode selectedNode = TreeLoggers.SelectedNode;
			if( selectedNode != null )
			{
				ResetLogLevel( selectedNode, newLevel );
				UpdateTree( selectedNode.Nodes );
			}
		}

		/// <summary>
		/// Handles the Click event of the ContextMenuResetChildren control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ContextMenuResetChildren_Click( object sender, EventArgs args )
		{
			TreeNode selectedNode = TreeLoggers.SelectedNode;
			if( selectedNode != null )
			{
				Level level = ( (Logger) selectedNode.Tag ).Level;

				// if this is the root node then set to get level from parent
				if( selectedNode.Parent == null )
				{
					level = null;
				}

				ResetAllChildren( selectedNode.Nodes, level );
			}
		}

		/// <summary>
		/// Resets all children.
		/// </summary>
		/// <param name="nodes">The nodes.</param>
		/// <param name="level">The level.</param>
		private void ResetAllChildren( TreeNodeCollection nodes, Level level )
		{
			foreach( TreeNode node in nodes )
			{
				ResetLogLevel( node, level );
				ResetAllChildren( node.Nodes, level );
			}
		}

		/// <summary>
		/// Resets the log level.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="level">The level.</param>
		private void ResetLogLevel( TreeNode node, string level )
		{
			ResetLogLevel( node, Level.Parse( level ) );
		}

		/// <summary>
		/// Resets the log level.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="level">The level.</param>
		private void ResetLogLevel( TreeNode node, Level level )
		{
			// Reset logger properties
			( (Logger) node.Tag ).Level = level;

			// Node properties
			UpdateNode( node );
		}

		/// <summary>
		/// Updates the tree.
		/// </summary>
		/// <param name="nodes">The nodes.</param>
		private void UpdateTree( TreeNodeCollection nodes )
		{
			foreach( TreeNode node in nodes )
			{
				UpdateNode( node );
				UpdateTree( node.Nodes );
			}
		}

		/// <summary>
		/// Updates the node.
		/// </summary>
		/// <param name="node">The node.</param>
		private void UpdateNode( TreeNode node )
		{
			Logger logger = (Logger) node.Tag;

			node.Text = GetDisplayName( logger );
			node.ImageIndex = node.SelectedImageIndex = ( logger.Level == null ? 0 : 1 );
		}

		/// <summary>
		/// Builds the drop list.
		/// </summary>
		private void BuildDropList()
		{
			ComboNamespace.Items.Clear();

			foreach( Assembly assembly in AppDomain.CurrentDomain.GetAssemblies() )
			{
				Type[] types = assembly.GetTypes();
				foreach( Type type in types )
				{
					if( TypeContainsLog4NetLogger( type ) )
					{
						ComboNamespace.Items.Add( type );
					}
				}
			}
		}

		/// <summary>
		/// Builds the context menu.
		/// </summary>
		private void BuildContextMenu()
		{
			ContextMenuLevel.Items.Clear();

			m_menuItemParent = new LevelMenuItem();
			m_menuItemParent.Text = "Inherent From Parent";
			m_menuItemParent.Tag = null;
			m_menuItemParent.Image = ImageListLogObjects.Images[ 0 ];
			m_menuItemParent.Click += new EventHandler( ContextMenuLevel_Click );
			ContextMenuLevel.Items.Add( m_menuItemParent );

			ToolStripMenuItem menuItemChildren = new ToolStripMenuItem();
			menuItemChildren.Text = "Reset All Children";
			menuItemChildren.Tag = null;
			menuItemChildren.Image = ImageListLogObjects.Images[ 1 ];
			menuItemChildren.Click += new EventHandler( ContextMenuResetChildren_Click );
			ContextMenuLevel.Items.Add( menuItemChildren );

			ContextMenuLevel.Items.Add( new ToolStripSeparator() );

			Level[] levels = s_standardLevels;
			if( CheckPredefinedLevels.Checked )
			{
				levels = Level.GetPredefinedLevels();
			}

			foreach( Level level in levels )
			{
				LevelMenuItem item = new LevelMenuItem();
				item.Text = level.ToString();
				item.Tag = level;
				item.Click += new EventHandler( ContextMenuLevel_Click );
				ContextMenuLevel.Items.Add( item );
			}
		}


		/// <summary>
		/// Builds the tree.
		/// </summary>
		private void BuildTree()
		{
			TreeLoggers.Nodes.Clear();

			// Build the Tree
			Dictionary<string, TreeNode> nodeList = new Dictionary<string, TreeNode>();

			Hierarchy hierarchy = (Hierarchy) LogManager.GetRepository();
			Logger rootLogger = hierarchy.Root;

			// Add root node
			TreeNode rootNode = new TreeNode();
			rootNode.Text = GetDisplayName( rootLogger );
			rootNode.Tag = rootLogger;
			rootNode.ImageIndex = 1;
			rootNode.SelectedImageIndex = 1;

			nodeList.Add( rootLogger.Name, rootNode );

			ILogger[] loggers = hierarchy.GetCurrentLoggers();
			foreach( ILogger iLogger in loggers )
			{
				Logger logger = iLogger as Logger;
				if( logger != null )
				{
					TreeNode node = new TreeNode();
					node.Text = GetDisplayName( logger );
					node.Tag = logger;
					node.ImageIndex = node.SelectedImageIndex = logger.Level == null ? 0 : 1;

					nodeList.Add( logger.Name, node );
				}
			}

			// Now that we have all the loggers we need to put them in the tree
			foreach( TreeNode node in nodeList.Values )
			{
				Logger logger = (Logger) node.Tag;
				if( logger.Parent != null )
				{
					string parentName = logger.Parent.Name;
					TreeNode parentNode = nodeList[ parentName ];
					parentNode.Nodes.Add( node );
				}
			}

			TreeLoggers.Nodes.Add( rootNode );
			TreeLoggers.ExpandAll();
		}

		/// <summary>
		/// Gets the name of the display.
		/// </summary>
		/// <param name="logger">The logger.</param>
		/// <returns></returns>
		private string GetDisplayName( Logger logger )
		{
			try
			{
				TypeName typeName = new TypeName( logger.Name );
				Level logLevel = GetLogLevel( logger );

				string displayName = typeName.DisplayName;
				if( logger.Name == "root" )
				{
					displayName = "Root Logger";
				}

				return string.Format( "{0} - {1}", logLevel, displayName );
			}
			catch
			{
			}

			return logger.Name;
		}

		/// <summary>
		/// Gets the log level.
		/// </summary>
		/// <param name="logger">The logger.</param>
		/// <returns></returns>
		private Level GetLogLevel( Logger logger )
		{
			Logger nextLogger = logger;
			
			while( nextLogger.Level == null )
			{
				nextLogger = nextLogger.Parent;
			}

			return nextLogger.Level;
		}

		/// <summary>
		/// Types the contains log4net logger.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		public bool TypeContainsLog4NetLogger( Type type )
		{
			FieldInfo[] fields = type.GetFields( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static );
			foreach( FieldInfo field in fields )
			{
				Type fieldType = field.FieldType;

				if( fieldType == typeof( ILog ) ||
					fieldType == typeof( ILogger ) ||
					fieldType == typeof( Logger ) )
				{
					return true;
				}
			}

			return false;
		}
	}
}